Source code for hysop.core.graph.node_requirements

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from abc import ABCMeta
from hysop.tools.htypes import check_instance, first_not_None
from hysop.tools.transposition_states import TranspositionState
from hysop.tools.numpywrappers import npw
from hysop.constants import MemoryOrdering


[docs] class NodeRequirements: __slots__ = ("_node",) def __init__(self, node): from hysop.core.graph.computational_node import ComputationalGraphNode check_instance(node, ComputationalGraphNode) self._node = node
[docs] def check_and_update_reqs(self, field_requirements): """Check and possibly update field requirements against global node requirements.""" pass
[docs] class OperatorRequirements(NodeRequirements): __slots__ = ( "_enforce_unique_transposition_state", "_enforce_unique_topology_shape", "_enforce_unique_memory_order", "_enforce_unique_ghosts", ) def __init__( self, operator, enforce_unique_transposition_state=None, enforce_unique_topology_shape=None, enforce_unique_memory_order=None, enforce_unique_ghosts=None, ): from hysop.core.graph.computational_operator import ComputationalGraphOperator check_instance(operator, ComputationalGraphOperator) super().__init__(node=operator) enforce_unique_transposition_state = first_not_None( enforce_unique_transposition_state, True ) enforce_unique_topology_shape = first_not_None( enforce_unique_topology_shape, True ) enforce_unique_memory_order = first_not_None(enforce_unique_memory_order, True) enforce_unique_ghosts = first_not_None(enforce_unique_ghosts, False) self.enforce_unique_transposition_state = enforce_unique_transposition_state self.enforce_unique_topology_shape = enforce_unique_topology_shape self.enforce_unique_memory_order = enforce_unique_memory_order self.enforce_unique_ghosts = enforce_unique_ghosts @property def operator(self): return self._node def _set_enforce_unique_transposition_state(self, val): check_instance(val, bool) self._enforce_unique_transposition_state = val def _get_enforce_unique_transposition_state(self): return self._enforce_unique_transposition_state enforce_unique_transposition_state = property( _get_enforce_unique_transposition_state, _set_enforce_unique_transposition_state ) def _set_enforce_unique_topology_shape(self, val): check_instance(val, bool) self._enforce_unique_topology_shape = val def _get_enforce_unique_topology_shape(self): return self._enforce_unique_topology_shape enforce_unique_topology_shape = property( _get_enforce_unique_topology_shape, _set_enforce_unique_topology_shape ) def _set_enforce_unique_memory_order(self, val): check_instance(val, bool) self._enforce_unique_memory_order = val def _get_enforce_unique_memory_order(self): return self._enforce_unique_memory_order enforce_unique_memory_order = property( _get_enforce_unique_memory_order, _set_enforce_unique_memory_order ) def _set_enforce_unique_ghosts(self, val): check_instance(val, bool) self._enforce_unique_ghosts = val def _get_enforce_unique_ghosts(self): return self._enforce_unique_ghosts enforce_unique_ghosts = property( _get_enforce_unique_ghosts, _set_enforce_unique_ghosts )
[docs] def check_and_update_reqs(self, field_requirements): """Check and possibly update field requirements against global node requirements.""" if field_requirements is None: raise RuntimeError axes = set() memory_order, can_split, min_ghosts, max_ghosts = None, None, None, None field_names = "" for is_input, freqs in field_requirements.iter_requirements(): if freqs is None: continue (field, td, req) = freqs if self.enforce_unique_transposition_state: if axes: if not axes.intersection(set(req.axes)): msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n" msg += "Previous axes: \n {} required by fields {}\nare incompatible " msg += ( "with axes requirements \n {} enforced by {} field {}.\n" ) msg = msg.format( tuple(axes), field_names, tuple(req.axes), "input" if is_input else "output", field.name, ) raise RuntimeError(msg) axes = axes.intersection(set(req.axes)) elif req.axes: axes = axes.union(set(req.axes)) if self.enforce_unique_memory_order: assert req.memory_order is not None if req.memory_order != MemoryOrdering.ANY: if memory_order is not None: if memory_order != req.memory_order: msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n" msg += "Previous memory order: \n {} required by fields {}\nare incompatible " msg += "with memory order requirements \n {} enforced by {} field {}.\n" msg = msg.format( memory_order, field_names, req.memory_order, "input" if is_input else "output", field.name, ) raise RuntimeError(msg) else: memory_order = req.memory_order if self.enforce_unique_topology_shape: assert req.can_split is not None if can_split is not None: if npw.sum(can_split * req.can_split) == 0: if all(can_split == req.can_split): pass else: msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n" msg += "Previous cartesian split directions: \n {} required by fields {}\nare incompatible " msg += "with cartesian split directions requirements \n {} enforced by {} field {}." msg = msg.format( can_split, field_names, req.can_split, "input" if is_input else "output", field.name, ) msg += "\nDomain cannot be splitted accross multiple processes.\n" raise RuntimeError(msg) else: can_split *= req.can_split else: can_split = req.can_split.copy() if self.enforce_unique_ghosts: ming, maxg = req.min_ghosts, req.max_ghosts if min_ghosts is not None: msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n" if any(maxg < min_ghosts): msg += "Previous cartesian min_ghosts: \n {} required by fields {}\nare incompatible " msg += "with cartesian max_ghosts requirements \n {} enforced by {} field {}.\n" msg = msg.format( min_ghosts, field_names, maxg, "input" if is_input else "output", field.name, ) raise RuntimeError(msg) elif any(ming > max_ghosts): msg += "Previous cartesian max_ghosts: \n {} required by fields {}\nare incompatible " msg += "with cartesian min_ghosts requirements \n {} enforced by {} field {}.\n" msg = msg.format( max_ghosts, field_names, ming, "input" if is_input else "output", field.name, ) raise RuntimeError(msg) else: min_ghosts = npw.maximum(min_ghosts, ming) max_ghosts = npw.minimum(max_ghosts, maxg) else: min_ghosts = ming max_ghosts = maxg assert all(max_ghosts >= min_ghosts) field_names += field.name + ", " axes = tuple(axes) has_single_input = len(field_requirements._input_field_requirements) <= 1 # enforce global operator requirements onto field requirements for is_input, freqs in field_requirements.iter_requirements(): if freqs is None: continue (field, td, req) = freqs # enforce transposition axes if self.enforce_unique_transposition_state: if axes: req.axes = (axes[0],) elif not has_single_input: req.axes = (TranspositionState[field.dim].default_axes(),) # enforce memory order if self.enforce_unique_memory_order: if memory_order is not None: req.memory_order = memory_order elif not has_single_input: req.memory_order = MemoryOrdering.C_CONTIGUOUS # enforce topology shape (indirectly by enforcing split directions) if self.enforce_unique_topology_shape: assert can_split is not None if sum(can_split) > 1: i = 0 while can_split[i] == 0: i += 1 can_split[i + 1 :] = 0 req.can_split = can_split # enforce ghosts by setting min and max to the same if self.enforce_unique_ghosts: req.min_ghosts = min_ghosts req.max_ghosts = min_ghosts